/*
 * AbstractIPPool.java Created on 27 Январь 2006 г., 3:31 To change this
 * template, choose Tools | Template Manager and
 * open the template in the editor.
 */

package org.dicr.util.net.ippool;

import java.util.*;

import org.apache.log4j.*;
import org.dicr.util.data.exc.*;
import org.dicr.util.net.*;

/**
 * Abstract IP Pool.
 * 
 * @author Igor A Tarasov &lt;java@dicr.org&gt;
 * @version 060125
 */
public abstract class AbstractIPPool implements IPPool {

	private static final Logger log = Logger.getLogger(AbstractIPPool.class);

	/** Available networks */
	protected Set<Net> networks = new HashSet<>();

	/** List of reserved IPs */
	protected Set<IP> reserved = new HashSet<>();

	/**
	 * Return available networks.
	 * 
	 * @return networks, which is used for IP-allocation
	 */
	public synchronized Set<Net> getAvailableNetworks() {
		return new HashSet<>(this.networks);
	}

	/**
	 * Set available networks.
	 * 
	 * @param availableNetworks
	 *            networks to allocate IP-address from.
	 */
	public synchronized void setAvailableNetworks(final Set<Net> availableNetworks) {
		if (availableNetworks == null) throw new IllegalArgumentException("null networks"); //$NON-NLS-1$
		this.networks = new HashSet<>(availableNetworks);
		final StringBuilder sb = new StringBuilder("available networks for allocation: "); //$NON-NLS-1$
		for (final Net net : this.networks)
			sb.append(net).append(" "); //$NON-NLS-1$
		AbstractIPPool.log.info(sb.toString());
	}

	/**
	 * Return reserverd IPs.
	 * 
	 * @return ips, which is reserved from allocation.
	 */
	public Set<IP> getReserverdIps() {
		return new HashSet<>(this.reserved);
	}

	/**
	 * Set reserved ips.
	 * 
	 * @param reservedIps
	 *            ips's which is reserved from allocation
	 */
	public synchronized void setReservedIps(final Set<IP> reservedIps) {
		this.reserved = reservedIps == null ? this.reserved = new HashSet<>() : new HashSet<>(reservedIps);
		final StringBuilder sb = new StringBuilder();
		for (final IP ip : this.reserved)
			sb.append(ip).append(" "); //$NON-NLS-1$
		AbstractIPPool.log.info("configured reserved ips: " + sb.toString()); //$NON-NLS-1$
	}

	/**
	 * Return currently allocated IPs
	 * 
	 * @return IPs, which is currently allocated
	 * @throws StorageException
	 *             if data load problem
	 */
	public abstract Set<IP> getAllocatedIps() throws StorageException;

	/**
	 * Store allocated IPs
	 * 
	 * @param ips
	 *            allocated ips
	 * @throws StorageException
	 *             if data store problem
	 */
	protected abstract void sotreAllocatedIPs(Set<IP> ips) throws StorageException;

	/**
	 * Allocate IP address from pool.
	 * 
	 * @return allocated IP-address
	 * @throws DataException
	 *             if no free ips or data load problem
	 * @see org.dicr.util.net.ippool.IPPool#allocate()
	 */
	@Override
	public synchronized IP allocate() throws DataException {
		IP ret = null;
		final Set<IP> allocated = this.getAllocatedIps();
		for (final Net net : this.networks) {
			int startip;
			int endip;
			if (net.getMask().getAddressCount() == 1) {
				startip = net.getIp().toInteger();
				endip = net.getIp().toInteger();
			} else {
				startip = net.getIp().toInteger() + 1;
				endip = net.getBroadcast().toInteger() - 1;
			}
			for (int ip = startip; ip <= endip; ip++) {
				final IP testip = new IP(ip);
				if (!allocated.contains(testip) && !this.reserved.contains(testip)) {
					allocated.add(testip);
					ret = testip;
					break;
				}
			}
			if (ret != null) break;
		}
		if (ret != null) {
			AbstractIPPool.log.trace("allocated IP-address: " + ret + ", total allocated: " + allocated.size() //$NON-NLS-1$ //$NON-NLS-2$
					+ ", networks: " + this.networks.size() + ", reserved: " + this.reserved.size()); //$NON-NLS-1$
			this.sotreAllocatedIPs(allocated);
		} else {
			AbstractIPPool.log.warn("no left addresses to allocate, total allocated: " + allocated.size());
			throw new UnderflowException("no free IP to allocate");
		}
		return ret;
	}

	/**
	 * Free IP address. Delete from allocated and return to pool free addresses.
	 * 
	 * @param ip
	 *            IP-address to free
	 * @throws StorageException
	 *             error communicating with storage
	 */
	@Override
	public synchronized void free(final IP ip) throws StorageException {
		if (ip == null) throw new IllegalArgumentException("null ip");
		final Set<IP> allocated = this.getAllocatedIps();
		allocated.remove(ip);
		AbstractIPPool.log.trace("freeing address: " + ip);
		this.sotreAllocatedIPs(allocated);
	}

	/**
	 * Reset (dismiss) all allocations. Return all allocated addresses back to
	 * the pool.
	 * 
	 * @throws StorageException
	 *             if data save problem
	 */
	@Override
	public void reset() throws StorageException {
		AbstractIPPool.log.debug("reseting pool to dismiss allocation");
		this.sotreAllocatedIPs(new HashSet<IP>());
	}
}
